package com.zonski.jbots.game;

import java.util.Hashtable;
import java.util.Enumeration;

import com.zonski.jbots.engine.Rectangle;
import com.zonski.jbots.engine.Entity;
import com.zonski.jbots.engine.EntityType;
import com.zonski.jbots.engine.EntityFactory;
import com.zonski.jbots.engine.Resource;
import com.zonski.jbots.engine.CollisionEffect;
import com.zonski.jbots.engine.Device;
import com.zonski.jbots.engine.entity.MovingEntity;
import com.zonski.jbots.engine.entity.AbstractMind;
import com.zonski.jbots.engine.entity.InstantMovementEffect;
import com.zonski.jbots.engine.entity.Mind;

import com.zonski.jbots.engine.collision.SlideCollisionEffect;
import com.zonski.jbots.engine.collision.NoCollisionEffect;
import com.zonski.jbots.engine.collision.LayerFilteringCollisionEffectProxy;
import com.zonski.jbots.engine.collision.CompositeCollisionEffect;

import com.zonski.jbots.game.entity.*;

import com.zonski.jbots.game.resources.*;

import com.zonski.jbots.game.collision.ChargeSappingCollisionEffect;
import com.zonski.jbots.game.collision.ChargeBoostingCollisionEffect;

/**
 * A hard coded implementation of entity factory specifically for use
 * within the jbots game
 */
public class JBotsEntityFactory implements EntityFactory
{
    private static final CollisionEffect IMMOVABLE_EFFECT = NoCollisionEffect.INSTANCE;
    private static final CollisionEffect LASER_EFFECT = NoCollisionEffect.INSTANCE;
    private static CollisionEffect COMET_EFFECT;
    private static CollisionEffect BOT_EFFECT;
    private static CollisionEffect OTHER_BOT_EFFECT;
    private static CollisionEffect BOMB_BOT_EFFECT;
    private static CollisionEffect CONVERTER_EFFECT;
    private static CollisionEffect BOMB_EFFECT;
    private static CollisionEffect TEMP_WALL_EFFECT;
    private static CollisionEffect CHARGE_STATION_EFFECT;
    private static CollisionEffect EGG_EFFECT;
    private static CollisionEffect TANK_EFFECT;

    static
    {
        // initialise the collision effects
        CompositeCollisionEffect compositeEffect =
            new CompositeCollisionEffect(SlideCollisionEffect.INSTANCE);
        compositeEffect.addEffect(JBotsConstants.BOT_ID,
                new ChargeSappingCollisionEffect(1, true));
        compositeEffect.addEffect(JBotsConstants.BOMB_BOT_ID,
                new ChargeSappingCollisionEffect(1, true));
        compositeEffect.addEffect(JBotsConstants.TURRET_BOT_ID,
                NoCollisionEffect.INSTANCE);
        compositeEffect.addEffect(JBotsConstants.BABY_BOT_ID,
                new ChargeSappingCollisionEffect(1, true));
        compositeEffect.addEffect(JBotsConstants.TANK_BOT_ID,
                new ChargeSappingCollisionEffect(2, true));
        compositeEffect.addEffect(JBotsConstants.CONVERTER_BOT_ID,
                new ChargeSappingCollisionEffect(1, true));
        compositeEffect.addEffect(JBotsConstants.EGG_ID,
                NoCollisionEffect.INSTANCE);
        compositeEffect.addEffect(JBotsConstants.PAIN_ID,
                NoCollisionEffect.INSTANCE);
        compositeEffect.addEffect(JBotsConstants.EXPLOSION_ID,
                new ChargeSappingCollisionEffect(12, true));
        compositeEffect.addEffect(JBotsConstants.COMET_ID,
                new LayerFilteringCollisionEffectProxy(
                    new ChargeSappingCollisionEffect(20, true)));
        compositeEffect.addEffect(JBotsConstants.LASER_ID,
                    new ChargeSappingCollisionEffect(3, true));
        compositeEffect.addEffect(JBotsConstants.BOMB_ID,
                NoCollisionEffect.INSTANCE);
        compositeEffect.addEffect(JBotsConstants.RECHARGE_ID,
                new ChargeSappingCollisionEffect(-1, true));
        compositeEffect.addEffect(JBotsConstants.CHARGE_ID,
                new ChargeSappingCollisionEffect(-50, true));
        compositeEffect.addEffect(JBotsConstants.CAPACITY_ID,
                NoCollisionEffect.INSTANCE);
        OTHER_BOT_EFFECT = new LayerFilteringCollisionEffectProxy(compositeEffect);

        compositeEffect = new CompositeCollisionEffect(OTHER_BOT_EFFECT);
        compositeEffect.addEffect(JBotsConstants.CAPACITY_ID,
                new ChargeBoostingCollisionEffect(JBotsConstants.MAX_CHARGE));
        BOT_EFFECT = compositeEffect;

        compositeEffect = new CompositeCollisionEffect(OTHER_BOT_EFFECT);
        compositeEffect.addEffect(JBotsConstants.EGG_ID,
                new LayerFilteringCollisionEffectProxy(
                        new NoCollisionEffect(CollisionEffect.SIMPLE_COLLISIONS)));
        compositeEffect.addEffect(JBotsConstants.BOMB_ID,
                new LayerFilteringCollisionEffectProxy(
                        new NoCollisionEffect(CollisionEffect.SIMPLE_COLLISIONS)));
        CONVERTER_EFFECT = compositeEffect;

        // tanks are much harder to kill with comets
        compositeEffect = new CompositeCollisionEffect(OTHER_BOT_EFFECT);
        compositeEffect.addEffect(JBotsConstants.COMET_ID,
                new LayerFilteringCollisionEffectProxy(
                    new ChargeSappingCollisionEffect(10, true)));
        TANK_EFFECT = compositeEffect;

        compositeEffect = new CompositeCollisionEffect(NoCollisionEffect.INSTANCE);
        compositeEffect.addEffect(JBotsConstants.BOT_ID,
                new ChargeSappingCollisionEffect(10, true));
        compositeEffect.addEffect(JBotsConstants.BOMB_BOT_ID,
                new ChargeSappingCollisionEffect(10, true));
        compositeEffect.addEffect(JBotsConstants.TURRET_BOT_ID,
                new ChargeSappingCollisionEffect(10, true));
        compositeEffect.addEffect(JBotsConstants.BABY_BOT_ID,
                new ChargeSappingCollisionEffect(10, true));
        compositeEffect.addEffect(JBotsConstants.TANK_BOT_ID,
                new ChargeSappingCollisionEffect(10, true));
        compositeEffect.addEffect(JBotsConstants.CONVERTER_BOT_ID,
                new ChargeSappingCollisionEffect(10, true));
        compositeEffect.addEffect(JBotsConstants.EXPLOSION_ID,
                new ChargeSappingCollisionEffect(20, true));
        compositeEffect.addEffect(JBotsConstants.COMET_ID,
                    new ChargeSappingCollisionEffect(40, true));
        compositeEffect.addEffect(JBotsConstants.LASER_ID,
                    new ChargeSappingCollisionEffect(8, true));
        compositeEffect.addEffect(JBotsConstants.ROCK_ID,  SlideCollisionEffect.INSTANCE);
        compositeEffect.addEffect(JBotsConstants.TEMP_ROCK_ID, SlideCollisionEffect.INSTANCE);
        BOMB_BOT_EFFECT = new LayerFilteringCollisionEffectProxy(compositeEffect);

        compositeEffect = new CompositeCollisionEffect(NoCollisionEffect.INSTANCE);
        compositeEffect.addEffect(JBotsConstants.EXPLOSION_ID,
                new ChargeSappingCollisionEffect(12, false));
        compositeEffect.addEffect(JBotsConstants.LASER_ID,
                new ChargeSappingCollisionEffect(5, false));
        BOMB_EFFECT = compositeEffect;

        TEMP_WALL_EFFECT = BOMB_EFFECT;

        compositeEffect = new CompositeCollisionEffect(NoCollisionEffect.INSTANCE);
        compositeEffect.addEffect(JBotsConstants.EXPLOSION_ID,
                new ChargeSappingCollisionEffect(40, false));
        compositeEffect.addEffect(JBotsConstants.LASER_ID,
                new ChargeSappingCollisionEffect(3, false));
        CHARGE_STATION_EFFECT = compositeEffect;

        compositeEffect = new CompositeCollisionEffect(NoCollisionEffect.INSTANCE);
        compositeEffect.addEffect(JBotsConstants.EXPLOSION_ID,
                new ChargeSappingCollisionEffect(20, false));
        compositeEffect.addEffect(JBotsConstants.LASER_ID,
                new ChargeSappingCollisionEffect(3, false));
        EGG_EFFECT = compositeEffect;

        ChargeSappingCollisionEffect cce = new ChargeSappingCollisionEffect(20, false);
        compositeEffect = new CompositeCollisionEffect(cce);
        compositeEffect.addEffect(JBotsConstants.COMET_ID, NoCollisionEffect.INSTANCE);
        compositeEffect.addEffect(JBotsConstants.BOMB_ID, NoCollisionEffect.INSTANCE);
        compositeEffect.addEffect(JBotsConstants.EGG_ID, NoCollisionEffect.INSTANCE);
        compositeEffect.addEffect(JBotsConstants.CHARGE_ID, NoCollisionEffect.INSTANCE);
        compositeEffect.addEffect(JBotsConstants.RECHARGE_ID, NoCollisionEffect.INSTANCE);
        compositeEffect.addEffect(JBotsConstants.CAPACITY_ID, NoCollisionEffect.INSTANCE);
        COMET_EFFECT = new LayerFilteringCollisionEffectProxy(compositeEffect);

    }

    private static final JBotsEntityDescription[] DESCRIPTIONS = {
        new JBotsEntityDescription(
                EntityType.getEntityType(JBotsConstants.BOT, JBotsConstants.BOT_ID), 10, 10, 3, 1, 100,
                BOT_EFFECT,
                new BadBotMind(JBotsConstants.BOMB_BOT)),
        new JBotsEntityDescription(
                EntityType.getEntityType(JBotsConstants.COMET, JBotsConstants.COMET_ID), 10, 10, 12, 1, 0,
                COMET_EFFECT,
                new CometMind()),
        new JBotsEntityDescription(
                EntityType.getEntityType(JBotsConstants.ROCK, JBotsConstants.ROCK_ID), 12, 12, 0, 0, 0,
                IMMOVABLE_EFFECT,
                null),
        new JBotsEntityDescription(
                EntityType.getEntityType(JBotsConstants.EDGE, JBotsConstants.EDGE_ID), 1, 1, 0, 0, 0,
                IMMOVABLE_EFFECT,
                null),
        new JBotsEntityDescription(
                EntityType.getEntityType(JBotsConstants.BOMB, JBotsConstants.BOMB_ID), 4, 4, 0, 0, 0,
                BOMB_EFFECT,
                new BombMind()),
        new JBotsEntityDescription(
                EntityType.getEntityType(JBotsConstants.EXPLOSION, JBotsConstants.EXPLOSION_ID), 1, 1, 0, 0, 0,
                NoCollisionEffect.INSTANCE,
                new ExplosionMind()),
        new JBotsEntityDescription(
                EntityType.getEntityType(JBotsConstants.EGG, JBotsConstants.EGG_ID), 0, 0, 0, 0, 0,
                EGG_EFFECT,
                new EggMind()),
        new JBotsEntityDescription(
                EntityType.getEntityType(JBotsConstants.BOMB_BOT, JBotsConstants.BOMB_BOT_ID), 10, 10, 4, 1, 10,
                BOMB_BOT_EFFECT,
                new BombBotMind()),
        new JBotsEntityDescription(
                EntityType.getEntityType(JBotsConstants.TURRET_BOT, JBotsConstants.TURRET_BOT_ID), 12, 12, 0, 1, 100,
                OTHER_BOT_EFFECT,
                new TurretMind()),
        new JBotsEntityDescription(
                EntityType.getEntityType(JBotsConstants.TANK_BOT, JBotsConstants.TANK_BOT_ID), 10, 10, 2, 1, 150,
                TANK_EFFECT,
                new TankBotMind()),
        new JBotsEntityDescription(
                EntityType.getEntityType(JBotsConstants.CONVERTER_BOT, JBotsConstants.CONVERTER_BOT_ID), 10, 10, 2, 1, 30,
                CONVERTER_EFFECT,
                new ConverterBotMind()),
        new JBotsEntityDescription(
                EntityType.getEntityType(JBotsConstants.BABY_BOT, JBotsConstants.BABY_BOT_ID), 10, 10, 2, 1, 60,
                OTHER_BOT_EFFECT,
                new BabyBotMind()),
        new JBotsEntityDescription(
                EntityType.getEntityType(JBotsConstants.TEMP_ROCK, JBotsConstants.TEMP_ROCK_ID), 12, 12, 0, 0, 1,
                TEMP_WALL_EFFECT,
                new TempRockMind()),
        new JBotsEntityDescription(
                EntityType.getEntityType(JBotsConstants.TREE, JBotsConstants.TREE_ROCK_ID), 12, 12, 0, 0, 1,
                TEMP_WALL_EFFECT,
                new TempRockMind()),
        new JBotsEntityDescription(
                EntityType.getEntityType(JBotsConstants.RECHARGE, JBotsConstants.RECHARGE_ID), 12, 12, 0, 0, 100,
                CHARGE_STATION_EFFECT,
                null),
        new JBotsEntityDescription(
                EntityType.getEntityType(JBotsConstants.CHARGE, JBotsConstants.CHARGE_ID), 5, 5, 0, 0, 15,
                CHARGE_STATION_EFFECT,
                new AbstractMind()),
        new JBotsEntityDescription(
                EntityType.getEntityType(JBotsConstants.CAPACITY, JBotsConstants.CAPACITY_ID), 7, 7, 0, 0, 50,
                CHARGE_STATION_EFFECT,
                new AbstractMind()),
        new JBotsEntityDescription(
                EntityType.getEntityType(JBotsConstants.LASER, JBotsConstants.LASER_ID), 2, 2, 0, 0, 0,
                LASER_EFFECT,
                new LaserMind()),
        new JBotsEntityDescription(
                EntityType.getEntityType(JBotsConstants.PAIN, JBotsConstants.PAIN_ID), 2, 2, 0, 0, 0,
                NoCollisionEffect.INSTANCE,
                new TempRockMind()),
    };


    private Hashtable descriptions;
    private Device device;

    public JBotsEntityFactory(Device device)
    {
        this.descriptions = new Hashtable();
        loadDescriptions();
        this.device = device;
    }

    private void loadDescriptions()
    {
        for(int i=0; i<DESCRIPTIONS.length; i++)
        {
            JBotsEntityDescription description;
            description = DESCRIPTIONS[i];
            this.descriptions.put(description.type.getName(), description);
        }

        // load additional information
        // BOT inate abilities
        {
            JBotsEntityDescription description;
            description = (JBotsEntityDescription)this.descriptions.get(
                    JBotsConstants.BOT
                    );
            description.resourceClasses = new Hashtable(9);

            description.resourceClasses.put(
                    JBotsConstants.BOMB,
                    new BombMaker());
            description.resourceClasses.put(
                    JBotsConstants.COMET,
                    new CometMaker(3));

            EggMaker bombEggMaker = new EggMaker();
            bombEggMaker.setMinCharge(20);
            bombEggMaker.setMaxCharge(35);
            bombEggMaker.setToCreate(JBotsConstants.BOMB_BOT);
            description.resourceClasses.put(
                    JBotsConstants.BOMB_BOT,
                    bombEggMaker);

            EggMaker wormEggMaker = new EggMaker();
            wormEggMaker.setMinCharge(30);
            wormEggMaker.setMaxCharge(30);
            wormEggMaker.setToCreate(JBotsConstants.CONVERTER_BOT);
            description.resourceClasses.put(
                    JBotsConstants.CONVERTER_BOT,
                    wormEggMaker);


            EggMaker babyEggMaker = new EggMaker();
            babyEggMaker.setMinCharge(50);
            babyEggMaker.setMaxCharge(150);
            babyEggMaker.setToCreate(JBotsConstants.BABY_BOT);
            description.resourceClasses.put(
                    JBotsConstants.BABY_BOT,
                    babyEggMaker);

            EggMaker turretEggMaker = new EggMaker();
            turretEggMaker.setMinCharge(100);
            turretEggMaker.setMaxCharge(150);
            turretEggMaker.setToCreate(JBotsConstants.TURRET_BOT);
            description.resourceClasses.put(
                    JBotsConstants.TURRET_BOT,
                    turretEggMaker);

            EggMaker tankEggMaker = new EggMaker();
            tankEggMaker.setMinCharge(150);
            tankEggMaker.setMaxCharge(300);
            tankEggMaker.setToCreate(JBotsConstants.TANK_BOT);
            description.resourceClasses.put(
                    JBotsConstants.TANK_BOT,
                    tankEggMaker);

            CompositeEggMaker eggMaker = new CompositeEggMaker();
            eggMaker.addEgg(20, 35, JBotsConstants.BOMB_BOT);
            eggMaker.addEgg(35, 60, JBotsConstants.CONVERTER_BOT);
            eggMaker.addEgg(60, 100, JBotsConstants.BABY_BOT);
            eggMaker.addEgg(100, 150, JBotsConstants.TURRET_BOT);
            eggMaker.addEgg(150, 250, JBotsConstants.TANK_BOT);
            eggMaker.addEgg(250, 300, JBotsConstants.BOT);
            description.resourceClasses.put(
                    JBotsConstants.EGG,
                    eggMaker
            );
        }

        // turret abilities
        {
            JBotsEntityDescription description;
            description = (JBotsEntityDescription)this.descriptions.get(
                    JBotsConstants.TURRET_BOT
                    );
            description.resourceClasses = new Hashtable(2);

            LaserGun gun = new LaserGun(1, JBotsConstants.LASER);
            gun.setKillOwner(true);
            description.resourceClasses.put(
                    JBotsConstants.GUN,
                    gun
            );

        }

        // tank abilities
        {
            JBotsEntityDescription description;
            description = (JBotsEntityDescription)this.descriptions.get(
                    JBotsConstants.TANK_BOT
                    );
            description.resourceClasses = new Hashtable(2);

            LaserGun gun = new LaserGun(1, JBotsConstants.LASER);
            gun.setKillOwner(true);
            description.resourceClasses.put(
                    JBotsConstants.GUN,
                    gun
            );

            CometMaker cometMaker = new CometMaker(1);
            cometMaker.setUpdatesBetweenCreations(30);
            cometMaker.setKillOwner(true);
            description.resourceClasses.put(
                    JBotsConstants.COMET,
                    cometMaker
            );

        }

        // baby abilities
        {
            JBotsEntityDescription description;
            description = (JBotsEntityDescription)this.descriptions.get(
                    JBotsConstants.BABY_BOT
                    );
            description.resourceClasses = new Hashtable(2);

            CometMaker cometMaker = new CometMaker(1);
            cometMaker.setUpdatesBetweenCreations(30);
            cometMaker.setKillOwner(true);
            description.resourceClasses.put(
                    JBotsConstants.COMET,
                    cometMaker
            );
        }

        // charge station
        {
            JBotsEntityDescription description;
            description = (JBotsEntityDescription)this.descriptions.get(
                    JBotsConstants.RECHARGE
                    );
            description.resourceClasses = new Hashtable(1);
        }

        // charge
        {
            JBotsEntityDescription description;
            description = (JBotsEntityDescription)this.descriptions.get(
                    JBotsConstants.CHARGE
                    );
            description.resourceClasses = new Hashtable(1);
        }

        {
            JBotsEntityDescription description;
            description = (JBotsEntityDescription)this.descriptions.get(
                    JBotsConstants.CAPACITY
                    );
            description.resourceClasses = new Hashtable(1);
        }

        // temporary walls
        {
            JBotsEntityDescription description;
            description = (JBotsEntityDescription)this.descriptions.get(
                    JBotsConstants.TEMP_ROCK
                    );
            description.resourceClasses = new Hashtable(1);
        }

    }

    public Entity createEntity(String name)
        throws Exception
    {
        JBotsEntityDescription description;
        description = (JBotsEntityDescription)this.descriptions.get(name);
        // dynamically load the entity and the mind
        Entity entity;
        if(description != null)
        {
            if(description.mindClass != null)
            {
                entity = new MovingEntity();
            }else{
                entity = new Entity();
            }
            entity.entityType = description.type;
            entity.entityType = description.type;
            entity.bounds = new Rectangle(0, 0, description.width, description.height);
            entity.setMaxCharge(description.charge);
            entity.setCharge(description.charge);

            if(entity instanceof MovingEntity)
            {
                if(description.mindClass != null)
                {
                    AbstractMind mind = (AbstractMind)description.mindClass.copy();
                    mind.setEntity(entity);
                    ((MovingEntity)entity).setMind(mind);
                    if(description.divide > 0)
                    {
                        InstantMovementEffect effect = new InstantMovementEffect(description.speed, description.divide);
                        ((MovingEntity)entity).setMovementEffect(effect);
                    }
                }
            }
            // load the resources
            if(description.resourceClasses != null)
            {
                Enumeration keys = description.resourceClasses.keys();
                while(keys.hasMoreElements())
                {
                    String key = (String)keys.nextElement();
                    Resource value = (Resource)description.resourceClasses.get(key);
                    entity.setResource(key, value.copy());
                }
            }
            entity.collisionEffect = description.collisionEffect;
            entity.setDevice(this.device);
        }else{
            throw new Exception("unknown entity "+name);
        }
        return entity;
    }

    private static class JBotsEntityDescription
    {
        public CollisionEffect collisionEffect;
        public EntityType type;
        public int width;
        public int height;
        public int speed;
        public int divide;
        public Mind mindClass;
        public Hashtable resourceClasses;
        public int charge;

        public JBotsEntityDescription(EntityType type, int width, int height,
                int speed, int divide, int charge,
                CollisionEffect collisionEffect,
                Mind mindClass)
        {
            this.type = type;
            this.width = width;
            this.height = height;
            this.mindClass = mindClass;
            this.speed = speed;
            this.divide = divide;
            this.charge = charge;
            this.collisionEffect = collisionEffect;
        }
    }
}
